// epoll.hpp(epoll 模型封装)
#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "log.hpp"

namespace limou {
    /* Epoll 模型的相关操作 */
    class Epoll {
    private:
        static const int g_size; // 256
        static const int g_timeout; // 5000

        
    public:
        // 创建 epoll 模型, 并且返回其 fd, 同时设置超时时间(默认 5000)
        Epoll(int timeout = g_timeout) : _timeout(timeout) {
            _epfd = epoll_create(g_size);
            if (_epfd < 0) {
                _log.LogMessage(FATAL, "创建了 epoll 模型失败, %s %d", __FILE__, __LINE__);
                exit(-1);
            }
            _log.LogMessage(DEBUG, "成功创建了一个 epoll 模型, %s %d", __FILE__, __LINE__);
        }

        // 销毁 epoll 模型
        ~Epoll() {
            close(_epfd);
            _log.LogMessage(DEBUG, "成功销毁了一个 epoll 模型, %s %d", __FILE__, __LINE__);
        }

        // 把描述符添加到 epoll 中, 关注其 events 事件(注意就是在 event 这里设置 EPOLLET 边缘触发模式)
        bool AddSockToEpoll(int sock, uint32_t events) {
            struct epoll_event ev;
            ev.events = events;
            ev.data.fd = sock;
            int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sock, &ev);
            if (n == 0) {
                _log.LogMessage(DEBUG, "根据用户指示事件, 把描述符添加到 epoll 模型中进行关注, %s %d", __FILE__, __LINE__);
                return true;
            }

            _log.LogMessage(DEBUG, "把描述符添加到 epoll 模型中进行关注时发送错误, %s %d", __FILE__, __LINE__);
            return false;
        }

        // 从 epoll 模型中获取就绪的描述符列表 revs 和返回就绪个数 n, 注意需要设置最多获取的个数 num, 根据 revs[0-n] 来获取对应的
        // (1)套接字 revs[i].fd
        // (2)事件 revs[i].events
        int WaitEpoll(struct epoll_event revs[], int num) { // revs 是输出型参数
            int n = epoll_wait(_epfd, revs, num, _timeout);
            if (n == 0) {
                _log.LogMessage(DEBUG, "超时等待..., %s %d", __FILE__, __LINE__);
                sleep(1);
            }
            else {
                _log.LogMessage(DEBUG, "已就绪描述符 %d 个, %s %d", n, __FILE__, __LINE__);
            }
            return n; // 返回就绪套接字的个数
        }

        // 对已关注描述符的原事件 events 基础上添加 ET 模式
        bool CtrlEpoll(int sock, uint32_t events) {
            events |= EPOLLET; // 保留原事件的同时加上 ET 工作模式

            struct epoll_event ev;
            ev.events = events; // 需要添加的事件
            ev.data.fd = sock;

            int n = epoll_ctl(_epfd, EPOLL_CTL_MOD, sock, &ev);

            if (n == 0) {
                _log.LogMessage(DEBUG, "对已关注描述符的原事件 events 基础上添加 ET 模式成功, %s %d", __FILE__, __LINE__);
                return true;
            }
            else {
                _log.LogMessage(DEBUG, "对已关注描述符的原事件 events 基础上添加 ET 模式失败, %s %d", __FILE__, __LINE__);
                return false;
            }
        }

        // 移除对描述符的关注
        bool DelFromEpoll(int sock) {
            int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
            if (n == 0) {
                _log.LogMessage(DEBUG, "移除对描述符的关注成功, %s %d", __FILE__, __LINE__);
                return true;
            } 
            else {
                _log.LogMessage(DEBUG, "移除对描述符的关注失败, %s %d", __FILE__, __LINE__);
                return false;
            }
        }
        

    private:
        int _epfd; // epoll 模型返回的 fd
        int _timeout; // 超时事件
        Log _log; // 日志对象
    };
    
    const int Epoll::g_size = 256;
    const int Epoll::g_timeout = 5000;
}